package com.yanyeori.framework.storage.service;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import com.aliyun.oss.OSS;
import com.aliyun.oss.common.utils.BinaryUtil;
import com.aliyun.oss.model.MatchMode;
import com.aliyun.oss.model.PolicyConditions;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.auth.sts.AssumeRoleRequest;
import com.aliyuncs.auth.sts.AssumeRoleResponse;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.http.MethodType;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.profile.IClientProfile;
import com.yanyeori.framework.storage.config.OSSProperties;
import com.yanyeori.framework.storage.controller.vo.STSInfo;
import com.yanyeori.framework.storage.controller.vo.UrlSignInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.File;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 文件中心接口
 *
 * @author chenkuan 2023/2/14
 */
@Service
public class StorageServiceImpl implements StorageService {

    @Autowired(required = false)
    private OSSProperties ossProperties;
    @Autowired(required = false)
    private OSS ossClient;

    /**
     * STS模式-获取STS授权信息
     */
    @Override
    public STSInfo getSTSInfo() {
        DefaultProfile.addEndpoint(ossProperties.getSts().getRegionId(), "Sts", ossProperties.getSts().getEndPoint());
        IClientProfile profile = DefaultProfile.getProfile(ossProperties.getSts().getRegionId(), ossProperties.getSts().getAccessKeyId(), ossProperties.getSts().getAccessKeySecret());
        // 构造client
        DefaultAcsClient client = new DefaultAcsClient(profile);
        final AssumeRoleRequest request = new AssumeRoleRequest();
        request.setSysMethod(MethodType.POST);
        request.setRoleArn(ossProperties.getSts().getRoleArn());
        request.setRoleSessionName(ossProperties.getSts().getRoleSessionName());
        // 设置临时访问凭证的有效时间
        request.setDurationSeconds(ossProperties.getSts().getDurationSeconds());
        AssumeRoleResponse response;
        try {
            response = client.getAcsResponse(request);
        } catch (ClientException e) {
            throw new RuntimeException(e);
        }
        return STSInfo.builder()
                .http(ossProperties.getHttp())
                .endPoint(ossProperties.getEndpoint())
                .bucketName(ossProperties.getBucketName())
                .regionId(ossProperties.getSts().getRegionId())
                .accessKeyId(response.getCredentials().getAccessKeyId())
                .accessKeySecret(response.getCredentials().getAccessKeySecret())
                .securityToken(response.getCredentials().getSecurityToken())
                .expiration(response.getCredentials().getExpiration())
                .dir(getPathPrefix())
                .build();
    }

    /**
     * 自签名模式-获取上传链接签名
     *
     * @param content 待签名内容
     */
    @Override
    public UrlSignInfo getSign(String content) {
        //签名算法的格式为signature = "OSS " + AccessKeyId + ":" + base64(hmac-sha1(AccessKeySecret, content))，其中content是已根据请求参数拼接后的字符串
        if (StrUtil.isBlank(content)) {
            content = IdUtil.fastSimpleUUID();
        }
        String encode = Base64.encode(SecureUtil.hmacSha1(ossProperties.getAccessKeySecret()).digest(content));
        String signature = StrUtil.format("{} {}:{}", "OSS", ossProperties.getAccessKeyId(), encode);
        return UrlSignInfo.builder()
                .signature(signature)
                .endPoint(ossProperties.getHttp() + ossProperties.getEndpoint())
                .bucketName(ossProperties.getBucketName())
                .build();
    }

    /**
     * 获取上传链接
     *
     * @param name 文件名
     */
    @Override
    public Map<String, String> getUploadUrl(String name) {
        String dir = getPathPrefix();
        long expireTimePoint = System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(ossProperties.getUrlExpiration());
        // PostObject请求最大可支持的文件大小为5 GB，即CONTENT_LENGTH_RANGE为5*1024*1024*1024。
        PolicyConditions policyConds = new PolicyConditions();
        policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);
        policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);

        String postPolicy = ossClient.generatePostPolicy(new Date(expireTimePoint), policyConds);
        byte[] binaryData = postPolicy.getBytes(StandardCharsets.UTF_8);
        String policy = BinaryUtil.toBase64String(binaryData);
        String signature = ossClient.calculatePostSignature(postPolicy);

        Map<String, String> map = new HashMap<>();
        map.put("OSSAccessKeyId", ossProperties.getAccessKeyId());
        map.put("policy", policy);
        map.put("Signature", signature);
        map.put("dir", dir);
        map.put("host", StrUtil.format("{}{}.{}", ossProperties.getHttp(), ossProperties.getBucketName(), ossProperties.getEndpoint()));
        map.put("expire", String.valueOf(expireTimePoint));
        map.put("key", dir + name);
        return map;
    }

    /**
     * 获取文件链接
     *
     * @param key 文件key
     * @return 访问地址
     */
    @Override
    public String getFileUrl(String key) {
        return getFileUrl(key, ossProperties.getUrlExpiration(), TimeUnit.MINUTES);
    }

    /**
     * 获取文件自定义有效期的链接
     *
     * @param key  文件key
     * @param time 过期时间
     * @param unit 时间单位
     * @return 文件链接
     */
    @Override
    public String getFileUrl(String key, long time, TimeUnit unit) {
        Date expiration = new Date(System.currentTimeMillis() + unit.toMillis(time));
        return ossClient.generatePresignedUrl(ossProperties.getBucketName(), key, expiration).toString();
    }

    /**
     * 批量获取文件链接
     *
     * @param keys 文件key列表
     * @return 文件链接Map
     */
    @Override
    public Map<String, String> listFileUrl(List<String> keys) {
        return listFileUrl(keys, ossProperties.getUrlExpiration(), TimeUnit.MINUTES);
    }

    /**
     * 批量获取文件自定义有效期的链接
     *
     * @param keys 文件key列表
     * @param time 过期时间
     * @param unit 时间单位
     * @return 文件链接Map
     */
    @Override
    public Map<String, String> listFileUrl(List<String> keys, long time, TimeUnit unit) {
        if (CollUtil.isEmpty(keys)) {
            return new HashMap<>(0);
        }
        return keys.stream().distinct().collect(Collectors.toMap(key -> key, key -> getFileUrl(key, time, unit)));
    }

    /**
     * 文件上传接口
     *
     * @param file     上传的文件流
     * @param filename 文件名
     * @return 文件key
     */
    @Override
    public String uploadFile(InputStream file, String filename) {
        String key = getPathPrefix() + filename;
        ossClient.putObject(ossProperties.getBucketName(), key, file);
        return key;
    }

    /**
     * 文件上传接口
     *
     * @param file     上传的文件
     * @param filename 文件名
     * @return 文件key
     */
    @Override
    public String uploadFile(File file, String filename) {
        String key = getPathPrefix() + filename;
        ossClient.putObject(ossProperties.getBucketName(), key, file);
        return key;
    }

    /**
     * 获取文件存放路径前缀
     */
    @Override
    public String getPathPrefix() {
        return StrUtil.format("{}/{}/", DateUtil.format(new Date(), DatePattern.PURE_DATE_FORMAT), IdUtil.simpleUUID());
    }

    /**
     * 判断文件是否存在
     *
     * @param key 文件key
     * @return boolean
     */
    @Override
    public boolean doesFileExist(String key) {
        return ossClient.doesObjectExist(ossProperties.getBucketName(), key);
    }
}
